/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.bookmarksframework.core
{
	import flash.events.Event;
	import flash.events.IEventDispatcher;
	import flash.utils.Dictionary;
	
	import org.bookmarksframework.event.FragmentCommitEvent;
	import org.bookmarksframework.rule.IRuleParser;
	import org.bookmarksframework.util.CallLater;
	
	[ExcludeClass]
	public class Committer implements ICommitter
	{
		protected var storage:IStorage;
		
		protected var dispatcher:IEventDispatcher;
		
		protected var counter:int = 0;
		
		protected var _info:CommitInfo = null;
		
		protected var _done:Boolean = false;

		public function get done():Boolean
		{
			return _done;
		}

		public function get info():CommitInfo
		{
			return _info;
		}
		
		protected var _affectedFragments:Vector.<IFragment>;

		public function get affectedFragments():Vector.<IFragment>
		{
			return _affectedFragments;
		}
		
		protected var _path:Path;
		
		public function get path():Path
		{
			return _path;
		}
		
		private var _delay:int = 1;
		
		public function get delay():int
		{
			return _delay;
		}
		
		public function set delay(value:int):void
		{
			_delay = value;
		}
		
		public function initialize(storage:IStorage, dispatcher:IEventDispatcher):void
		{
			if(!done)
			{
				this.storage = storage;
				this.dispatcher = dispatcher;
			}
		}
		
		public function commit(path:Path, fragment:IFragment = null):CommitInfo
		{
			var result:CommitInfo = null;
			
			if (!done)
			{
				var children:Vector.<FacileHierarchy>;
				_info = new CommitInfo(fragment, path);
				_affectedFragments = new Vector.<IFragment>();
				result = info;
				counter = 0;
				
				if (fragment && storage.contains(fragment))
				{
					children = new Vector.<FacileHierarchy>();
					children.push(storage.getItem(storage.getKey(fragment)));
				}
				else
				{
					children = storage.getChildren();  
				}
				
				_path = path;
				CallLater.execute2(commitHierarchy, 
					[children, path.getChildren()], delay);
			}
			
			return result;
		}
		
		protected function commitHierarchy(controllers:Vector.<FacileHierarchy>, 
										   path:Vector.<FacileHierarchy>):void
		{
			var controller:IFragmentController;
			var committed:Boolean;
			
			for (var i:int = 0; i < controllers.length; ++i)
			{
				if (i <= (path.length - 1))
				{
					controller = controllers[i].value as IFragmentController;
					committed = commitFragment(controller.fragment, path[i].value);
					this.path.updateFragment(path[i], controller.fragment);
					
					if (committed)
					{
						counter++;
						CallLater.execute2(postCommitHierarchy, 
							[path[i], controllers[i]], delay);
					}
				}
				else
				{
					break;
				}
			}
			
			if (!counter)
			{
				CallLater.execute2(complete);
			}
		}
		
		protected function postCommitHierarchy(path:FacileHierarchy, 
											   controller:FacileHierarchy):void
		{	
			var pathChildren:Vector.<FacileHierarchy>;
			var controllerChildren:Vector.<FacileHierarchy>;
			pathChildren = path.getChildren();
			controllerChildren = controller.getChildren();
			
			counter--;
			
			if (controllerChildren.length > 1)
				controllerChildren = filterControllers(controllerChildren);
			
			if (pathChildren.length && 
				controllerChildren.length)
			{
				commitHierarchy(controllerChildren, pathChildren);
			}
			else
			{
				if (!counter)
				{
					CallLater.execute2(complete);
				}
			}
		}
		
		private function filterControllers(controllers:Vector.<FacileHierarchy>):Vector.<FacileHierarchy>
		{
			var result:Vector.<FacileHierarchy> = new Vector.<FacileHierarchy>();
			var dictionary:Dictionary = new Dictionary();
			var controllersLen:int = controllers.length;
			var controller:IFragmentController;
			var parent:IFragmentController;
			var parentCounter:int = 0;
			var byParent:Array = [];
			var byParentLen:int;
			var f:*;
			
			// TODO: improve
			for (var i:int = 0; i < controllersLen; ++i)
			{
				controller = controllers[i].value as IFragmentController;
				f = controllers[i].getParent();
				parent = f.value as IFragmentController;
				
				if (!parent)
					continue;
				
				if (dictionary[parent] == null)
				{
					dictionary[parent] = parentCounter++;
					byParent[dictionary[parent]] = new Vector.<FacileHierarchy>();
				}
				
				byParent[dictionary[parent]].push(controllers[i]);
			}
			
			byParentLen = byParent.length;
			
			for (i = 0; i < byParentLen; ++i)
			{
				if (byParent[i].length > 1)
				{
					byParent[i] = byParent[i].filter(filterActiveControllers); 
				}
			}
			
			for (i = 0; i < byParentLen; ++i)
			{
				result = result.concat(byParent[i]);
			}
			
			return result;
		}
		
		private function filterActiveControllers(item:*, index:int, 
												 array:Vector.<FacileHierarchy>):Boolean
		{
			return IFragmentController(item.value).fragment.active;
		}
		
		protected function commitFragment(fragment:IFragment, path:*):Boolean
		{
			var result:Boolean = false;
			var parser:IRuleParser = fragment.ruleParser.newInstance() as IRuleParser;
			var isCommitPrevented:Boolean = false;
			var evt:Event;
			var parsedValue:*;
			var oldValue:*;
			var oldPath:*;
			var value:*;
			
			parser.rule = fragment.rule;
			
			if (parser.validate(path))
			{
				value = parser.findValue(path);
				
				if (value !== null)
				{
					evt = new FragmentCommitEvent(fragment, path, value);
					if (fragment.hasEventListener(FragmentCommitEvent.COMMIT))
					{
						fragment.dispatchEvent(evt);
						isCommitPrevented = evt.isDefaultPrevented();
					}
					
					if (info.hasEventListener(FragmentCommitEvent.COMMIT))
					{
						evt = evt.clone();
						info.dispatchEvent(evt);
						isCommitPrevented = isCommitPrevented || evt.isDefaultPrevented();
					}
					
					if (dispatcher.hasEventListener(FragmentCommitEvent.COMMIT))
					{
						evt = evt.clone();
						dispatcher.dispatchEvent(evt);
						isCommitPrevented = isCommitPrevented || evt.isDefaultPrevented();
					}
					affectedFragments.push(fragment);
					result = !isCommitPrevented;
					
					if (result)
					{
						oldValue = fragment.value;
						oldPath = fragment.part;
						fragment.value = value;
						
						return true;
					}
				}
			}
			
			return false;
		}
		
		protected function complete():void
		{
			if (!done)
			{
				_done = true;
				info.dispatchEvent(new Event(Event.COMPLETE));
				dispatcher.dispatchEvent(new Event(Event.COMPLETE));
			}
		}
		
	}
}